package com.swt.gap.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.swt.gap.common.FileTypeEnum;
import com.swt.gap.common.constant.GraphDbConstant;
import com.swt.gap.common.NodeTypeEnum;
import com.swt.gap.common.graph.GraphNeo4jVO;
import com.swt.gap.common.graph.GraphProcessor;
import com.swt.gap.common.graph.Neo4jPath;
import com.swt.gap.common.graph.Neo4jTools;
import com.swt.gap.config.ServerConfig;
import com.swt.gap.model.graphmodel.FileName;
import com.swt.gap.model.vo.graph.*;
import com.swt.gap.service.GraphDataService;
import com.swt.gap.service.GraphNodeService;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author sx
 */
@Service
public class GraphNodeServiceImpl implements GraphNodeService {
    private final GraphProcessor graphProcessor;
    private final GraphDataService graphDataService;
    private final ServerConfig serverConfig;
    private final Neo4jTools neo4jTools;

    public GraphNodeServiceImpl(GraphProcessor graphProcessor,
                                GraphDataService graphDataService,
                                ServerConfig serverConfig,
                                Neo4jTools neo4jTools) {
        this.graphProcessor = graphProcessor;
        this.graphDataService = graphDataService;
        this.serverConfig = serverConfig;
        this.neo4jTools = neo4jTools;
    }

    @Override
    public NodeInfoVO getNodeInfo(String nodeId) {
        NodeInfoVO retValue = new NodeInfoVO();
        String cypher = "match (n) where n.id ='" + nodeId + "' return n";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        if (!graph.getNodes().isEmpty()) {
            cypher = "match (n)-[]-(m:`实体`) where n.id ='" + nodeId + "' return count(m) as res";
            retValue.setEntityCount((long)neo4jTools.getOneResultByCypher(cypher).get(0));
            cypher = "match (n)-[]-(m:`事件`) where n.id ='" + nodeId + "' return count(m) as res";
            retValue.setEventCount((long)neo4jTools.getOneResultByCypher(cypher).get(0));
            cypher = "match (n)-[]-(m:`文档`) where n.id ='" + nodeId + "' return count(m) as res";
            retValue.setDocCount((long)neo4jTools.getOneResultByCypher(cypher).get(0));

            Map<String, Object> props = graph.getNodes().get(0).getProperties();
            retValue.setName((String) props.get(GraphDbConstant.PROPERTY_NAME));
            retValue.setDescription((String) props.get(GraphDbConstant.PROPERTY_DESCRIPTION));
            if (props.containsKey(GraphDbConstant.PROPERTY_KEY_PIC_URL)) {
                retValue.setPicUrl((String) props.get(GraphDbConstant.PROPERTY_KEY_PIC_URL));
            } else {
                retValue.setPicUrl(graph.getNodes().get(0).getIcon());
            }


            Integer nodeType = 0;
            List<String> labels = graph.getNodes().get(0).getLabels();
            for (String label : labels) {
                nodeType = NodeTypeEnum.getCodeByName(label);
                if (nodeType > 0) {
                    retValue.setNodeType(nodeType);
                    break;
                }
            }
            /*if (nodeType > 0) {
                retValue.setSummary("test");
                retValue.setKeywords(Arrays.asList("test1,test2".split(",")));
            }*/
        }
        return retValue;
    }

    @Override
    public List<NodeProperty> listNodeProperties(String nodeId) {
        List<NodeProperty> retValue = new ArrayList<>();
        String cypher = "match (n) where n.id ='" + nodeId + "' return n";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        if (!graph.getNodes().isEmpty()) {
            Map<String, Object> props = graph.getNodes().get(0).getProperties();
            retValue = transferMapPropToNodeProperty(props);
        }
        //去掉固定的用来显示节点文字的 name 属性
        for(NodeProperty nodeProperty: retValue) {
            if (nodeProperty.getPropertyKey().equals(GraphDbConstant.PROPERTY_NAME)) {
                retValue.remove(nodeProperty);
                break;
            }
        }
        return retValue;
    }

    @Override
    public List<NodeRelationshipsVO> getRelationNode(String nodeId) {
        List<NodeRelationshipsVO> retValue = new ArrayList<>();

        String cypher = "MATCH p=(n)-[]-() where n.id ='" + nodeId + "' return p";
        GraphNeo4jVO graph = graphProcessor.getPathByCypher2(cypher);
        List<GraphNeo4jVO.Node> nodes = graph.getNodes();
        List<GraphNeo4jVO.Link> links = graph.getLinks();
        Map<String, List<GraphNeo4jVO.Link>> mapLinks = links.stream()
                .collect(Collectors.groupingBy(GraphNeo4jVO.Link::getType));
        mapLinks.forEach((k,v) -> {
            NodeRelationshipsVO nodeRelationships = new NodeRelationshipsVO();
            nodeRelationships.setRelationshipType(k);
            nodeRelationships.setRelationshipTypeCount(v.size());
            List<NodeEntity> nodeEntities = new ArrayList<>();
            v.forEach(link -> {
                NodeEntity relNode = new NodeEntity();
                String relNodeId = "";
                if (!nodeId.equals(link.getStartNodeId())) {
                    relNodeId = link.getStartNodeId();
                } else if (!nodeId.equals(link.getEndNodeId())) {
                    relNodeId = link.getEndNodeId();
                }
                for (GraphNeo4jVO.Node node : nodes) {
                    if (relNodeId.equals(node.getId())) {
//                        relNode.setName((String)node.getProperties().get(GraphDbConstant.PROPERTY_NAME));
                        if (node.getProperties().containsKey(GraphDbConstant.PROPERTY_KEY_NAME)) {
                            relNode.setName((String)node.getProperties().get(GraphDbConstant.PROPERTY_KEY_NAME));
                        } else if (node.getProperties().containsKey(GraphDbConstant.PROPERTY_NAME)) {
                            relNode.setName((String)node.getProperties().get(GraphDbConstant.PROPERTY_NAME));
                        }
                        relNode.setDescription((String)node.getProperties().get(GraphDbConstant.PROPERTY_DESCRIPTION));

                        List<String> labels = node.getLabels();
                        for (String label : labels) {
                            if (NodeTypeEnum.getCodeByName(label) == 0) {
                                relNode.setNodeType(label);
                                break;
                            }
                        }
                        /*List<NodeProperty> nodeProperties = new ArrayList<>();
                        Map<String, Object> mapProps = node.getProperties();
                        mapProps.forEach((key,value) -> {
                            NodeProperty nodeProperty = new NodeProperty(key, String.valueOf(value));
                            nodeProperties.add(nodeProperty);
                        });
                        relNode.setProperties(nodeProperties);*/
                        break;
                    }
                }
                nodeEntities.add(relNode);
            });
            nodeRelationships.setNodeEntities(nodeEntities);
            retValue.add(nodeRelationships);
        });
        Collections.sort(retValue, (arg0, arg1) -> arg0.getRelationshipTypeCount().compareTo(arg1.getRelationshipTypeCount()));
        return retValue;
    }

    @Override
    public List<NodeEntity> getNodeRelEntity(String nodeId) {
        List<NodeEntity> retValue = new ArrayList<>();
        String cypher = "MATCH p=(m)-[]-(n:" + NodeTypeEnum.ENTITY.getName() + ") where m.id ='" + nodeId + "' return n";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        List<GraphNeo4jVO.Node> nodes = graph.getNodes();
        nodes.forEach(node -> {
            NodeEntity relNode = new NodeEntity();
//            relNode.setName((String)node.getProperties().get(GraphDbConstant.PROPERTY_NAME));
            if (node.getProperties().containsKey(GraphDbConstant.PROPERTY_KEY_NAME)) {
                relNode.setName((String)node.getProperties().get(GraphDbConstant.PROPERTY_KEY_NAME));
            } else if (node.getProperties().containsKey(GraphDbConstant.PROPERTY_NAME)) {
                relNode.setName((String)node.getProperties().get(GraphDbConstant.PROPERTY_NAME));
            }
            relNode.setDescription((String)node.getProperties().get(GraphDbConstant.PROPERTY_DESCRIPTION));
            List<String> labels = node.getLabels();
            for (String label : labels) {
                if (NodeTypeEnum.getCodeByName(label) == 0) {
                    relNode.setNodeType(label);
                    break;
                }
            }

            relNode.setProperties(transferMapPropToNodeProperty(node.getProperties()));
            retValue.add(relNode);
        });
        return retValue;
    }

    @Override
    public List<GraphNeo4jVO.Node> getNodeRelEntityRetGraphNode(String nodeId) {
        String cypher = "MATCH p=(m)-[]-(n:" + NodeTypeEnum.ENTITY.getName() + ") where m.id ='" + nodeId + "' return n";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        return  graph.getNodes();
    }

    @Override
    public List<NodeEventVO> getNodeRelEvent(String nodeId) {
        List<NodeEventVO> retValue = new ArrayList<>();
        String cypher = "MATCH (startNode)-[relationship]-(endNode:" + NodeTypeEnum.EVENT.getName() +
                ") where startNode.id ='" + nodeId + "' return startNode, endNode, relationship";
        List<Neo4jPath> paths = graphProcessor.getNeo4jPathByCypher(cypher);
        paths.forEach(path -> {
            NodeEventVO nodeEvent = new NodeEventVO();
            GraphNeo4jVO.Node endNode = path.getEndNode();
            Map<String, Object> props = endNode.getProperties();
            if (props.containsKey(GraphDbConstant.PROPERTY_NAME)) {
                nodeEvent.setName((String) props.get(GraphDbConstant.PROPERTY_NAME));
            } else if (props.containsKey(GraphDbConstant.PROPERTY_KEY_NAME)) {
                nodeEvent.setName((String)props.get(GraphDbConstant.PROPERTY_KEY_NAME));
            }
            if (props.containsKey(GraphDbConstant.PROPERTY_DESCRIPTION)) {
                nodeEvent.setDescription((String) props.get(GraphDbConstant.PROPERTY_DESCRIPTION));
            }
            if (props.containsKey(GraphDbConstant.PROPERTY_KEY_START_TIME)) {
                nodeEvent.setStartTime((String) props.get(GraphDbConstant.PROPERTY_KEY_START_TIME));
            }
            if (props.containsKey(GraphDbConstant.PROPERTY_KEY_END_TIME)) {
                nodeEvent.setEndTime((String) props.get(GraphDbConstant.PROPERTY_KEY_END_TIME));
            }
            endNode.getLabels().forEach(label -> {
                if (NodeTypeEnum.getCodeByName(label) == 0) {
                    nodeEvent.setNodeType(label);
                }
            });
            nodeEvent.setRelType(path.getRelationship().getType());
            retValue.add(nodeEvent);
        });
        return retValue;
    }

    @Override
    public List<GraphNeo4jVO.Node> getNodeRelEventRetGraphNode(String nodeId) {
        String cypher = "MATCH p=(m)-[]-(n:" + NodeTypeEnum.EVENT.getName() + ") where m.id ='" + nodeId + "' return n";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        return  graph.getNodes();
    }

    @Override
    public List<NodeDocVO> getNodeRelDoc(String nodeId) {

        List<NodeDocVO> retValue = new ArrayList<>();
        String cypher = "MATCH (startNode)-[relationship]-(endNode:" + NodeTypeEnum.DOC.getName() +
                ") where startNode.id ='" + nodeId + "' return startNode, endNode, relationship";
        List<Neo4jPath> paths = graphProcessor.getNeo4jPathByCypher(cypher);
        paths.forEach(path -> {
            NodeDocVO nodeDoc = new NodeDocVO();
            GraphNeo4jVO.Node endNode = path.getEndNode();
            nodeDoc = transferNodeToNodeDocVO(endNode.getProperties());
            retValue.add(nodeDoc);

        });
        return retValue;
    }

    @Override
    public List<GraphNeo4jVO.Node> getNodeRelDocRetGraphNode(String nodeId) {
        String cypher = "MATCH p=(m)-[]-(n:" + NodeTypeEnum.DOC.getName() + ") where m.id ='" + nodeId + "' return n";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        return  graph.getNodes();
    }

    @Override
    public NodeDocVO getNodeRelDocContent(String nodeId) {
        NodeDocVO nodeDoc = new NodeDocVO();
        String cypher = "MATCH (startNode:" + NodeTypeEnum.DOC.getName() +
                ")-[relationship]-(endNode) where startNode.id ='" + nodeId + "' return startNode, endNode, relationship";
        List<Neo4jPath> paths = graphProcessor.getNeo4jPathByCypher(cypher);
        if (!paths.isEmpty()) {
            Neo4jPath path = paths.get(0);
            GraphNeo4jVO.Node startNode = path.getStartNode();
            nodeDoc = transferNodeToNodeDocVO(startNode.getProperties());
        }
        return nodeDoc;
    }

    @Override
    public NodeDocVO getContentByNode(Map<String, Object> props) {
        return transferNodeToNodeDocVO(props);
    }

    @Override
    public List<GraphNeo4jVO.Node> listNodeByName(String nodeName) {
        String cypher = "MATCH (n) where n." + GraphDbConstant.PROPERTY_KEY_NAME+ " =~ '.*" + nodeName + ".*' return n limit 20";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        return graph.getNodes();
    }

    @Override
    public GraphNeo4jVO list1Path(String nodeId) {
        String cypher = "MATCH p=(n)--(m) where n." + GraphDbConstant.PROPERTY_KEY_ID + " = '" + nodeId +
                "'  and not('" + NodeTypeEnum.EVENT.getName() + "' in labels(m))  return p";
        return graphProcessor.getPathByCypher(cypher);
    }

    @Override
    public List<GraphNeo4jVO.Node> listMediaNodeByName(String nodeName) {
        String cypher = "MATCH (m:" + NodeTypeEnum.DOC.getName()
                + ") where m." + GraphDbConstant.PROPERTY_KEY_NAME+ " =~ '.*" + nodeName +
                ".*' with distinct(m.id) as id, min(m) as n return  n limit 20";
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        return graph.getNodes();
    }

    public NodeDocVO transferNodeToNodeDocVO(Map<String, Object> props) {
        NodeDocVO retValue = new NodeDocVO();
        JSONArray jaSource = new JSONArray();
        String savedFileName = ".";
        if(props.get(GraphDbConstant.PROPERTY_KEY_SOURCE).getClass().equals(String.class)){
            jaSource = JSON.parseArray((String)props.get(GraphDbConstant.PROPERTY_KEY_SOURCE));
            retValue.setName((String) props.get(GraphDbConstant.PROPERTY_NAME));
            FileName fileName = JSON.toJavaObject((JSONObject)jaSource.get(0), FileName.class);
            savedFileName = fileName.getSaveFileName();
        } else if(props.get(GraphDbConstant.PROPERTY_KEY_SOURCE).getClass().equals(ArrayList.class)) {
            ArrayList<HashMap> fileNames = (ArrayList)props.get(GraphDbConstant.PROPERTY_KEY_SOURCE);
            savedFileName = (String)fileNames.get(0).get("saveFileName");
        }
        if (".".equals(savedFileName) && props.containsKey(GraphDbConstant.PROPERTY_KEY_ICON)) {
            savedFileName = (String)props.get(GraphDbConstant.PROPERTY_KEY_ICON);
            if (savedFileName.contains("upload/")) {
                savedFileName = savedFileName.substring(savedFileName.indexOf("upload/") + "upload/".length());
            }
        }
        if (!".".equals(savedFileName)) {
            String suffix = savedFileName.substring(savedFileName.lastIndexOf("."));
            retValue.setDocType(FileTypeEnum.getFileTypeCodeBySuffix(suffix));
            retValue.setUrl(serverConfig.parseViewUrl(savedFileName, 1));

            switch (retValue.getDocType()) {
                case 1:
                    retValue.setFormContent(graphDataService.getFormFileData(savedFileName));
                    break;
                case 2:
                    try {
                        retValue.setTextContent(graphDataService.getTextFileData(savedFileName));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
        }
        return retValue;
    }

    private List<NodeProperty> transferMapPropToNodeProperty(Map<String, Object> mapProps) {
        List<NodeProperty> nodeProperties = new ArrayList<>();
        mapProps.forEach((key,value) -> {
            if (key.equals(GraphDbConstant.PROPERTY_KEY_SOURCE)) {
                StringBuilder sb = new StringBuilder();
                JSONArray sourceValues = JSON.parseArray(String.valueOf(value));
                for (int i = 0; i < sourceValues.size(); i++) {
                    FileName fileName = JSON.toJavaObject(((JSONObject)sourceValues.get(i)), FileName.class);
                    sb.append(fileName.getDisplayFileName());
                    if (i < sourceValues.size() - 1) {
                        sb.append(",");
                    }
                }

                NodeProperty nodeProperty = new NodeProperty(key, sb.toString());
                nodeProperties.add(nodeProperty);
            } else if (!key.equals(GraphDbConstant.PROPERTY_KEY_ID) && !key.equals(GraphDbConstant.PROPERTY_KEY_TASK_ID)
                    && !key.equals(GraphDbConstant.PROPERTY_KEY_FILE_TYPE) && !key.equals(GraphDbConstant.PROPERTY_AMOUNT)) {
                NodeProperty nodeProperty = new NodeProperty(key, String.valueOf(value));
                nodeProperties.add(nodeProperty);
            }
        });
        return nodeProperties;
    }

}
